#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include "CIpodLog.h"
#include "CIpodChip.h"
#include <pthread.h>
#include <sys/system_properties.h>

#ifdef _I2C_CHIP_20B
#define CHIP_SLAVE  0x11
#else
#define CHIP_SLAVE  0x10
#endif

#define kMFiAuthDevicePath					    "/dev/i2c-0"
#define kMFiAuthDeviceAddress			        0x10
#define kMFiAuthDeviceAddress2			        0x11

#define kMFiAuthRetryDelayMics					5000 // 5 ms.

#define kMFiAuthReg_ProtocolMajorVersion		0x02
#define kMFiAuthReg_AuthControlStatus			0x10
#define kMFiAuthFlagError						0x80
#define kMFiAuthControl_GenerateSignature		1
#define kMFiAuthReg_SignatureSize				0x11
#define kMFiAuthReg_SignatureData				0x12
#define kMFiAuthReg_ChallengeSize				0x20
#define kMFiAuthReg_ChallengeData				0x21
#define kMFiAuthReg_DeviceCertificateSize		0x30
#define kMFiAuthReg_DeviceCertificateData1		0x31 // Note: auto-increments so next read is Data2, Data3, etc.

static uint8_t *		gMFiCertificatePtr = NULL;
static size_t			gMFiCertificateLen = 0;

static uint8_t *        gAuthenticationSerialNumber = NULL;
static uint8_t          gMFiChipAddress = 0x10;

extern pthread_mutex_t authMutex;

int CIpodChip::s_ChipFd = -1;

int CIpodChip::init() {
    int ret = -1;
    char *chipPath = getenv("IPOD_ACCESSORY_CHIP_I2C_STR");
    if (chipPath == NULL) {
        CIpodLog::e("CIpodChip::init, chip path is null!\n");
        return -1;
    }

    CIpodLog::i("CIpodChip::init, chipPath %s CHIP_SLAVE = %d\n", chipPath, CHIP_SLAVE );
    if ((s_ChipFd = open(chipPath, O_RDWR)) < 0) {
        CIpodLog::e("CIpodChip::init, chip open failed! chipPath is %s.\n", chipPath);
        return -1;
    }
    ret = ioctl(s_ChipFd, I2C_TENBIT, 0);           //not 10bit
    ret = ioctl(s_ChipFd, I2C_SLAVE, CHIP_SLAVE);   //设置I2C从设备地址[6:0]
    if (ret == -1) {
        CIpodLog::e("CIpodChip::init, chip init failed!\n");
    } else {
        CIpodLog::i("CIpodChip::init, chip init success! s_ChipFd = %d\n", s_ChipFd);
    }
    return ret;
}

int CIpodChip::deinit() {
    int ret = -1;
    if (s_ChipFd != -1) {
        ret = close(s_ChipFd);
        s_ChipFd = -1;
        CIpodLog::i("CIpodChip::deinit, chip close! ret = %d\n", ret);
    }
    CIpodLog::i("CIpodChip::deinit, exit ok!\n");
    return ret;
}

int CIpodChip::send(uint8 regvalue, const uint8 *buf, uint16 len) {
    int ret = -1;
    if (s_ChipFd != -1) {
        uint8 *writebuf = (uint8 *) malloc(len + 1);
        *writebuf = regvalue;
        memcpy(writebuf + 1, buf, len);
        ret = write(s_ChipFd, writebuf, len + 1);
        if (ret == -1) {
            ret = write(s_ChipFd, writebuf, len + 1);
        }
        if (ret == -1) {
            CIpodLog::e("CIpodChip::send, (reg = 0x%x) ERROR(errno = %d) !\n", regvalue, errno);
        }
        if(writebuf != NULL){
            free(writebuf);
        }else{
            CIpodLog::e("CIpodChip::send, chip writebuf is null!\n");
        }
        usleep(1000);
    } else {
        CIpodLog::i("CIpodChip::send, chip has not been opened!\n");
    }
    return ret;
}

int CIpodChip::recv(uint8 regvalue, uint8 *buf, uint16 len) {
    int res = -1;

    if(s_ChipFd == -1){
        CIpodLog::i("CIpodChip::recv, chip has not been opened!\n");
        return -1;
    }
    res = write( s_ChipFd, &regvalue, 1 );
    if( res == -1 )
    {
        usleep(2000);
        if(s_ChipFd == -1) return -1;
        res = write( s_ChipFd, &regvalue, 1 );
    }

    if( res == -1 )
    {
      CIpodLog::e("CIpodChip::recv, (reg = 0x%x) ERROR(errno = %d) !\n", regvalue, errno);
      return -1;
    }

    usleep(3000);

    for( int i = 0; i <= len/256; i++ )
    {
        if(s_ChipFd == -1) return -1;
        res = read( s_ChipFd, buf + i * 256, (i == len/256)?(len - i * 256):256);
    }

    if( res == -1 )
    {
      CIpodLog::e("CIpodChip::send, (reg = 0x%x) ERROR(errno = %d) !\n", regvalue, errno);
      return -1;
    }

    usleep( 1000 );
    return res;
}

//buf size is too big and only read 256 byte
#if 0
int CIpodChip::recv(uint8 regvalue, uint8 *buf, uint16 len) {
    int ret = -1;
    if (s_ChipFd != -1) {
        ret = write(s_ChipFd, &regvalue, 1);
        if (ret == -1) {
            usleep(2000);
            ret = write(s_ChipFd, &regvalue, 1);
        }
        if (ret == -1) {
            CIpodLog::e("CIpodChip::recv, send (reg = 0x%x) ERROR(errno = %d) !\n", regvalue, errno);
        } else {
            usleep(3000);
            ret = read(s_ChipFd, buf, len);
            if (ret == -1) {
                CIpodLog::e("CIpodChip::recv, recv (reg = 0x%x) ERROR(errno = %d) !\n", regvalue, errno);
            }
            usleep(1000);
        }
    } else {
        CIpodLog::i("CIpodChip::recv, chip has not been opened!\n");
    }
    return ret;
}
#endif

static void
	iAP2_DoI2C( 
		int				inFD, 
		uint8_t			inRegister, 
		const uint8_t *	inWritePtr, 
		size_t			inWriteLen, 
		uint8_t *		inReadBuf, 
		size_t			inReadLen )
{
	int				tries;
    int             temp;
	ssize_t			n;
	uint8_t			buf[ 1 + inWriteLen ];
	size_t			len;

    if( inReadBuf )
    {
        // Combined mode transactions are not supported so set the register and do a separate read.

        for( tries = 1; tries < 40; tries++ )
        {
            n = write( inFD, &inRegister, 1 );
            // printf( "I2C Read_f Write inRegister: %02X, n: %d.\n", inRegister, n );
            if( n == 1 )    break;

            usleep( kMFiAuthRetryDelayMics );
        }

        n = 0;
        for( tries = 1; tries < 40; tries++ )
        {
//			for( size_t i = 0; i <= inReadLen / 256; i++ )
//			{
//				if( i == inReadLen / 256 )
//				{
//					temp = read( inFD, inReadBuf + i * 256, inReadLen - i * 256 );
//					if( temp == -1 )
//					{
//						i--;
//					}
//					else
//					{
//						n = n + temp;
//					}
//				}
//				else
//				{
//					temp = read( inFD, inReadBuf + i * 256, 256 );
//					if( temp == -1 )
//					{
//						i--;
//					}
//					else
//					{
//						n = n + temp;
//					}
//				}
//			}
			n = read( inFD, inReadBuf, inReadLen );
            // printf( "I2C Read_f Read inRegister: %02X, n: %d, inReadLen: %d, inReadBuf: %s.\n", inRegister, n, inReadLen, inReadBuf );
            if( n == ( (ssize_t) inReadLen ) )  break;

            usleep( kMFiAuthRetryDelayMics );
        }
    }
    else
    {
        // Gather the register and data so it can be done as a single write transaction.

        buf[ 0 ] = inRegister;
        memcpy( &buf[ 1 ], inWritePtr, inWriteLen );
        len = 1 + inWriteLen;

        for( tries = 1; tries < 4; tries++ )
        {
            n = write( inFD, buf, len );
            // printf( "I2C Write_f Write inRegister: %02X, n: %d, len: %d.\n", inRegister, n, len );
            if( n == (ssize_t) len )    break;

            usleep( kMFiAuthRetryDelayMics );
        }
    }
}

void  CIpodChip::MFiPlatform_CopyCertificate( uint8_t **outCertificateSNPtr, uint8_t **outCertificatePtr, size_t *outCertificateLen )
{
	size_t			certificateLen;
	uint8_t *		certificatePtr;
    uint8_t *       certificateSNPtr;
	int				fd = -1;
	uint8_t			buf[ 2 ];

    // If the certificate has already been cached then return that as an optimization since it doesn't change.

    if( gMFiCertificateLen > 0 )
    {
        *outCertificateSNPtr = gAuthenticationSerialNumber;
        *outCertificatePtr = gMFiCertificatePtr;
        *outCertificateLen = gMFiCertificateLen;
        return;
    }

    pthread_mutex_lock( &authMutex );

    char mfi_auth_path[70] = {0};
    __system_property_get("sys.mfi.auth.path", mfi_auth_path);
    if( strlen( mfi_auth_path ) > 0 ) {
    	fd = open( mfi_auth_path, O_RDWR );
    }else{
    	fd = open( kMFiAuthDevicePath, O_RDWR );
    }


    ioctl( fd, I2C_SLAVE, kMFiAuthDeviceAddress );

    //Tinker patch >>>
    iAP2_DoI2C( fd, kMFiAuthReg_DeviceCertificateSize, NULL, 0, buf, 2 );
    certificateLen = ( buf[ 0 ] << 8 ) | buf[ 1 ];
    if( certificateLen == 0 ) {
    	 ioctl( fd, I2C_SLAVE, kMFiAuthDeviceAddress2 );
    	 gMFiChipAddress = kMFiAuthDeviceAddress2;
    	 __system_property_set("sys.xq.mfi.addr", "1");
    }
    //Tinker patch <<<

    certificateSNPtr = (uint8_t *) malloc( 32 );
    iAP2_DoI2C( fd, 0x4E, NULL, 0, certificateSNPtr, 32 );

    iAP2_DoI2C( fd, kMFiAuthReg_DeviceCertificateSize, NULL, 0, buf, 2 );
    certificateLen = ( buf[ 0 ] << 8 ) | buf[ 1 ];

    certificatePtr = (uint8_t *) malloc( certificateLen );

    // Note: reads from the data1 register auto-increment to data2, data3, etc. registers that follow it.

    iAP2_DoI2C( fd, kMFiAuthReg_DeviceCertificateData1, NULL, 0, certificatePtr, certificateLen );

    *outCertificatePtr = certificatePtr;
    *outCertificateLen = certificateLen;
    *outCertificateSNPtr  = certificateSNPtr;

    if( fd >= 0 )	close( fd );

    pthread_mutex_unlock( &authMutex );
}

void    CIpodChip::MFiPlatform_Initialize()
{
    MFiPlatform_CopyCertificate( &gAuthenticationSerialNumber, &gMFiCertificatePtr, &gMFiCertificateLen );
}

static int iAP2_UseAuth3( int inFD )
{
	static int gUseAuth3 = -1;
	
	if( gUseAuth3 == -1 )
	{
		uint8_t buf[1];
		iAP2_DoI2C( inFD, kMFiAuthReg_ProtocolMajorVersion, NULL, 0, buf, 1 );

        gUseAuth3 = ( buf[0] == 0x03 ? 1 : 0 );
        CIpodLog::i( "Using MFi Auth v.%d chip\n", buf[0] );
	}
	
	return gUseAuth3;
}

void  CIpodChip::MFiPlatform_CreateSignature(
        const uint8_t *	inDigestPtr, 
        size_t			inDigestLen, 
        uint8_t **		outSignaturePtr,
        size_t *		outSignatureLen )
{
    int             err;
	int				fd;
	uint8_t			buf[ 32 ];
	size_t			signatureLen;
	uint8_t *		signaturePtr;

    pthread_mutex_lock( &authMutex );
    char mfi_auth_path[70] = {0};
    __system_property_get("sys.mfi.auth.path", mfi_auth_path);
    if( strlen( mfi_auth_path ) > 0 ) {
    	fd = open( mfi_auth_path, O_RDWR );
    }else{
    	fd = open( kMFiAuthDevicePath, O_RDWR );
    }


//    err = ioctl( fd, I2C_SLAVE, kMFiAuthDeviceAddress );
    err = ioctl( fd, I2C_SLAVE, gMFiChipAddress ); //Tinker


    // Write the data to sign.
    // Note: writes to the size register auto-increment to the data register that follows it.

    iAP2_DoI2C( fd, kMFiAuthReg_ChallengeData, inDigestPtr, inDigestLen, NULL, 0 );

    // Generate the signature.

    buf[ 0 ] = kMFiAuthControl_GenerateSignature;
    iAP2_DoI2C( fd, kMFiAuthReg_AuthControlStatus, buf, 1, NULL, 0 );

    iAP2_DoI2C( fd, kMFiAuthReg_AuthControlStatus, NULL, 0, buf, 1 );

    // Read the signature.

    iAP2_DoI2C( fd, kMFiAuthReg_SignatureSize, NULL, 0, buf, 2 );
    signatureLen = ( buf[ 0 ] << 8 ) | buf[ 1 ];

    signaturePtr = (uint8_t *) malloc( signatureLen );
    iAP2_DoI2C( fd, kMFiAuthReg_SignatureData, NULL, 0, signaturePtr, signatureLen );

    *outSignaturePtr = signaturePtr;
    *outSignatureLen = signatureLen;

    if( fd >= 0 );	close( fd );
    pthread_mutex_unlock( &authMutex );
}
